#include <dohome_error_code.h>
#include <dohome_hal_network.h>
#include <dohome_type.h>
#include <http_parser/http_parser.h>
#ifndef __DOHOME_LIB_HTTP_CLIENT_H__
#define __DOHOME_LIB_HTTP_CLIENT_H__

#ifdef __cplusplus
    extern "C" {
#endif
#ifdef DOHOME_LIB_HTTPCLIENT_ISGLOBAL
#define DOHOME_LIB_HTTPCLIENT_GLOBAL_FLAG extern
#else
#define DOHOME_LIB_HTTPCLIENT_GLOBAL_FLAG 
#endif

#define HTTP_PORT 80
#define HTTPS_PORT  433
#define OK 0
 
#define MIN(x,y) (((x)<(y))?(x):(y))
#define MAX(x,y) (((x)>(y))?(x):(y))

// HTTP_CLIENT_DEFAULT_TIMEOUT second
#define HTTP_CLIENT_DEFAULT_TIMEOUT 15000
#define CHUNK_SIZE      (1024)
#define SEND_BUF_SIZE   512

typedef dohome_op_ret (*dohome_http_finish_cb_t)(void *arg);

///HTTP client results

/** @brief HTTP_RES_T enum, with inline docs */
typedef enum {
    DOHOME_HTTP_PROCESSING, ///<Processing
    DOHOME_HTTP_PARSE, ///<url Parse error
    DOHOME_HTTP_DNS, ///<Could not resolve name
    DOHOME_HTTP_PRTCL, ///<Protocol error
    DOHOME_HTTP_NOTFOUND, ///<HTTP 404 Error
    DOHOME_HTTP_REFUSED, ///<HTTP 403 Error
    DOHOME_HTTP_ERROR, ///<HTTP xxx error
    DOHOME_HTTP_TIMEOUT, ///<Connection timeout
    DOHOME_HTTP_CONN, ///<Connection error
    DOHOME_HTTP_CLOSED, ///<Connection was closed by remote host
    DOHOME_HTTP_REDIRECT, ///<HTTP 300 - 303
    DOHOME_HTTP_OK = 0, ///<Success
}HTTP_RES_T;

typedef enum {
    DOHOME_HTTP_GET,
    DOHOME_HTTP_POST,
    DOHOME_HTTP_PUT,
    DOHOME_HTTP_DELETE,
    DOHOME_HTTP_HEAD
}HTTP_METH_T;

typedef void(*IHTTPDATAIN_READ_RESET_FUNC_T)(void *arg);
typedef DOHOME_INT_T(*IHTTPDATAIN_READ_FUNC_T)(void *arg, DOHOME_CHAR_T* buf, DOHOME_SIZE_T len, DOHOME_SIZE_T* pReadLen);
typedef DOHOME_INT_T(*IHTTPDATAIN_GET_DATATYPE_FUNC_T)(void *arg, DOHOME_CHAR_T* type, DOHOME_SIZE_T maxTypeLen);
typedef DOHOME_UINT8_T(*IHTTPDATAIN_GET_ISCHUNKED_FUNC_T)(void *arg);
typedef DOHOME_SIZE_T(*IHTTPDATAIN_GET_DATALEN_FUNC_T)(void *arg);

typedef struct 
{
  
  /** Reset stream to its beginning 
   * Called by the HTTPClient on each new request
   */
  IHTTPDATAIN_READ_RESET_FUNC_T readReset;

  /** Read a piece of data to be transmitted
   * @param buf Pointer to the buffer on which to copy the data
   * @param len Length of the buffer
   * @param pReadLen Pointer to the variable on which the actual copied data length will be stored
   */
  IHTTPDATAIN_READ_FUNC_T read;
  
  /** Get MIME type
   * @param type Internet media type from Content-Type header
   */
  IHTTPDATAIN_GET_DATATYPE_FUNC_T getDataType; //Internet media type for Content-Type header
  
  /** Determine whether the HTTP client should chunk the data
   *  Used for Transfer-Encoding header
   */
  IHTTPDATAIN_GET_ISCHUNKED_FUNC_T getIsChunked;
  
  /** If the data is not chunked, get its size
   *  Used for Content-Length header
   */
  IHTTPDATAIN_GET_DATALEN_FUNC_T getDataLen;

}IHTTPDataOut_T;

///This is a simple interface for HTTP data storage (impl examples are Key/Value Pairs, File, etc...)
// typedef void(*IHTTPDATAOUT_WRITE_RESET_FUNC_T)();
// typedef DOHOME_INT_T(*IHTTPDATAOUT_WRITE_FUNC_T)(DOHOME_CONST DOHOME_CHAR_T* buf, DOHOME_SIZE_T len);
// typedef void(*IHTTPDATAOUT_SET_DATATYPE_FUNC_T)(DOHOME_CONST DOHOME_CHAR_T* type);
// typedef void(*IHTTPDATAOUT_SET_ISCHUNKED_FUNC_T)(DOHOME_UINT8_T chunked);
// typedef void(*IHTTPDATAOUT_SET_DATALEN_FUNC_T)(DOHOME_SIZE_T len);
// typedef struct  
// {
//   /** Reset stream to its beginning 
//    * Called by the HTTPClient on each new request
//    */
//   IHTTPDATAOUT_WRITE_RESET_FUNC_T writeReset;

//   /** Write a piece of data transmitted by the server
//    * @param buf Pointer to the buffer from which to copy the data
//    * @param len Length of the buffer
//    */
//   IHTTPDATAOUT_WRITE_FUNC_T write;

//   /** Set MIME type
//    * @param type Internet media type from Content-Type header
//    */
//   IHTTPDATAOUT_SET_DATATYPE_FUNC_T setDataType;

//   /** Determine whether the data is chunked
//    *  Recovered from Transfer-Encoding header
//    */
//   IHTTPDATAOUT_SET_ISCHUNKED_FUNC_T setIsChunked;
  
//   /** If the data is not chunked, set its size
//    * From Content-Length header
//    */
//   IHTTPDATAOUT_SET_DATALEN_FUNC_T setDataLen;

// }IHTTPDataIn_T;

typedef enum {
    HTTP_STA_CONN           = 0,
    HTTP_STA_WAIT_FOR_CONN  = 1,
    HTTP_STA_SEND_REQ       = 2,
    HTTP_STA_SEND_DEFHEADER = 3,
    HTTP_STA_SEND_USRHEADER = 4,
    HTTP_STA_SEND_CLSHEADER = 5,
    HTTP_STA_SEND_DAT       = 6,
    HTTP_STA_RECV_RESP      = 7,
    HTTP_STA_PARSE_RESP     = 8,
    HTTP_STA_GET_HEADER     = 9,
    HTTP_STA_GET_ALL_DATA   = 10,

    HTTP_STA_CLS            = 11,//must be last one
    HTTP_STA_CLSD           = 12,//must be last one
}HTTP_STA_T;

typedef struct{
    HTTP_STA_T              sta;
    void                    *arg;
    DOHOME_INT_T              m_timeout;

    DOHOME_CONST DOHOME_CHAR_T  *url;
    HTTP_METH_T             method;
    IHTTPDataOut_T          *pDataOut;
    // IHTTPDataIn_T           *pDataIn;

    DOHOME_CONST DOHOME_CHAR_T  *m_basicAuthUser;
    DOHOME_CONST DOHOME_CHAR_T  *m_basicAuthPassword;
    DOHOME_INT_T              m_httpResponseCode;

    DOHOME_CONST DOHOME_CHAR_T  *header;
    DOHOME_CHAR_T             *redirect_url;
    DOHOME_INT_T              redirect_url_size;
    DOHOME_INT_T              redirect;

    // tmp var
    DOHOME_CHAR_T             scheme[8];
    DOHOME_INT_T              retry;
    DOHOME_CHAR_T             host[32];
    DOHOME_CHAR_T             path[160];
    DOHOME_UINT16_T           port;
    HTTP_RES_T              res;
    DOHOME_INT_T              ret;
    DOHOME_CHAR_T             buf[CHUNK_SIZE];
    DOHOME_CONST DOHOME_CHAR_T  *meth;
    DOHOME_CHAR_T             type[48];
    DOHOME_SIZE_T             trfLen;
    DOHOME_SIZE_T             writtenLen;
    DOHOME_CHAR_T             chunkHeader[64];
    DOHOME_CHAR_T             *crlfPtr;
    dohome_op_ret             conn_ret;
    dohome_op_ret             send_ret;

    http_parser_settings    settings;
    http_parser             parser;

    // dohome_op_ret             recv_ret;
    // DOHOME_INT_T              crlfPos;

    // DOHOME_CHAR_T             *temp_recv_resp_buf_p;
    DOHOME_CHAR_T             *temp_recv_buf_p;
    // DOHOME_SIZE_T             temp_recv_len;
    DOHOME_SIZE_T             temp_recv_less_len;
    // DOHOME_SIZE_T             temp_recvContentLength;
    // DOHOME_UINT8_T            temp_recvChunked;
    // DOHOME_UINT8_T            temp_checkChunked;
    // DOHOME_CHAR_T             key[32];
    // DOHOME_CHAR_T             value[32];
    // DOHOME_INT_T              temp_n;
    // DOHOME_UINT8_T            foundCrlf;
    // DOHOME_SIZE_T             temp_readLen;
    // DOHOME_UINT8_T            temp_if_reset_before_recv_chunk;
    DOHOME_CHAR_T              *post_buf;
    DOHOME_SIZE_T              post_buf_index;
    DOHOME_SIZE_T              post_len;
    DOHOME_CHAR_T              *resp_buf;
    DOHOME_SIZE_T              resp_buf_len;
    dohome_http_finish_cb_t   http_finish_cb;
}HTTP_CLIENT_T;

DOHOME_LIB_HTTPCLIENT_GLOBAL_FLAG \
    HTTP_RES_T dohome_http_connect(HTTP_CLIENT_T *client);

DOHOME_LIB_HTTPCLIENT_GLOBAL_FLAG \
    void dohome_lib_httpc_close(HTTP_CLIENT_T *client);

#ifdef __cplusplus
}
#endif

#endif // __DOHOME_LIB_HTTP_CLIENT_H__